﻿

function adr(node, validate) {
    if (node) {
        Microformats.parser.newMicroformat(this, node, "adr", validate);
    }
}

adr.prototype.toString = function () {
    var address_text = "";
    var start_parens = false;
    if (this["street-address"]) {
        address_text += this["street-address"][0];
    } else if (this["extended-address"]) {
        address_text += this["extended-address"];
    }
    if (this["locality"]) {
        if (this["street-address"] || this["extended-address"]) {
            address_text += " (";
            start_parens = true;
        }
        address_text += this["locality"];
    }
    if (this["region"]) {
        if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
            address_text += " (";
            start_parens = true;
        } else if (this["locality"]) {
            address_text += ", ";
        }
        address_text += this["region"];
    }
    if (this["country-name"]) {
        if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
            address_text += " (";
            start_parens = true;
            address_text += this["country-name"];
        } else if ((!this["locality"]) && (!this["region"])) {
            address_text += this["country-name"];
        } else if (((!this["locality"]) && (this["region"])) || ((this["locality"]) && (!this["region"]))) {
            address_text += ", ";
            address_text += this["country-name"];
        }
    }
    if (start_parens) {
        address_text += ")";
    }
    return address_text;
}

var adr_definition = {
    mfObject: adr,
    className: "adr",
    properties: {
        "type": {
            plural: true,
            values: ["work", "home", "pref", "postal", "dom", "intl", "parcel"]
        },
        "post-office-box": {
    },
    "street-address": {
        plural: true
    },
    "extended-address": {
},
"locality": {
},
"region": {
},
"postal-code": {
},
"country-name": {
}
},
validate: function (node) {
    /* Changed to DOM based query - Glenn Jones */
    var count = Microformats.childernCountByClass(node, ' post-office-box ');
    count += Microformats.childernCountByClass(node, ' street-address ');
    count += Microformats.childernCountByClass(node, ' extended-address ');
    count += Microformats.childernCountByClass(node, ' locality ');
    count += Microformats.childernCountByClass(node, ' region ');
    count += Microformats.childernCountByClass(node, ' postal-code ');
    count += Microformats.childernCountByClass(node, ' country-name ');
    if (count > 0) {
        return true;
    } else {
        throw ("Unable to create microformat");
    }
}
};

Microformats.add("adr", adr_definition);

function hCard(node, validate) {
    if (node) {
        Microformats.parser.newMicroformat(this, node, "hCard", validate);
    }
}
hCard.prototype.toString = function () {
    if (this.resolvedNode) {
        /* If this microformat has an include pattern, put the */
        /* organization-name in parenthesis after the fn to differentiate */
        /* them. */
        var fns = Microformats.getElementsByClassName(this.node, "fn");
        if (fns.length === 0) {
            if (this.fn) {
                if (this.org && this.org[0]["organization-name"] && (this.fn != this.org[0]["organization-name"])) {
                    return this.fn + " (" + this.org[0]["organization-name"] + ")";
                }
            }
        }
    }
    return this.fn;
}


//----------------------------------------------------

function Test(node, validate) {
    if (node) {
        Microformats.parser.newMicroformat(this, node, "Test", validate);
    }
}


var Test_definition = {
    mfObject: Test,
    className: "test",
    required: ["displayName"],
    get: function () { return value },
    value: {},
    properties: {
        "urls": {
            plural: true,
            datatype: "anyURI",
            get: function () { return value },
            value: {}
        },
        "displayName": {
            required: true,
            get: function () { return value },
            value: {}
        }
    }
}

Microformats.add("Test", Test_definition);




function PoCo(node, validate) {
    if (node) {
        Microformats.parser.newMicroformat(this, node, "PoCo", validate);
    }
}


var PoCo_definition = {
    mfObject: PoCo,
    className: "poco",
    required: ["displayName"],
    properties: {
        "urls": {
            plural: true,
            datatype: "anyURI"
        },
        "phoneNumbers": {
            subproperties: {
                "type": {
                    plural: true,
                    values: ["msg", "home", "work", "pref", "voice", "fax", "cell", "video", "pager", "bbs", "car", "isdn", "pcs"]
                },
                "value": {
                    datatype: "phoneNumbers",
                    virtual: true
                }
            },
            plural: true
        },
        "accounts": {
            subproperties: {
                "domain": {},
                "username": {},
                "userid": {}
            },
            plural: true
        },
        "emails": {
            subproperties: {
                "type": {
                    plural: true,
                    values: ["internet", "x400", "pref"]
                },
                "value": {
                    datatype: "email",
                    virtual: true
                }
            },
            plural: true
        },
        "displayName": {
            required: true
        }
    }
}

Microformats.add("PoCo", PoCo_definition);

//----------------------------------------------------






var hCard_definition = {
    mfObject: hCard,
    className: "vcard",
    required: ["fn"],
    properties: {
        "adr": {
            plural: true,
            datatype: "microformat",
            microformat: "adr"
        },
        "agent": {
            plural: true,
            datatype: "microformat",
            microformat: "hCard"
        },
        "bday": {
            datatype: "dateTime"
        },
        "class": {
    },
    "category": {
        plural: true,
        datatype: "microformat",
        microformat: "tag",
        microformat_property: "tag"
    },
    "email": {
        subproperties: {
            "type": {
                plural: true,
                values: ["internet", "x400", "pref"]
            },
            "value": {
                datatype: "email",
                virtual: true
            }
        },
        plural: true
    },
    "fn": {
        required: true,
        virtual: true,
        virtualGetter: function (mfnode) {
            /* Changed to DOM based query - Glenn Jones */
            var givenName = Microformats.getElementsByClassName(mfnode, "given-name");
            var additionalName = Microformats.getElementsByClassName(mfnode, "additional-name");
            var familyName = Microformats.getElementsByClassName(mfnode, "family-name");
            var fn = '';

            if (Microformats.getTextContent(givenName) != '')
                fn += givenName + ' ';

            if (Microformats.getTextContent(additionalName) != '')
                fn += additionalName + ' ';

            if (Microformats.getTextContent(familyName) != '')
                fn += familyName + ' ';

            if (fn != '')
                return fn.substring(0, fn.length - 1);
            else
                return undefined;

        }
    },
    "geo": {
        datatype: "microformat",
        microformat: "geo"
    },
    "key": {
        plural: true
    },
    "label": {
        plural: true
    },
    "logo": {
        plural: true,
        datatype: "anyURI"
    },
    "mailer": {
        plural: true
    },
    "n": {
        subproperties: {
            "honorific-prefix": {
                plural: true
            },
            "given-name": {
                plural: true
            },
            "additional-name": {
                plural: true
            },
            "family-name": {
                plural: true
            },
            "honorific-suffix": {
                plural: true
            }
        },
        virtual: true,
        /*  Implied "n" Optimization */
        /* http://microformats.org/wiki/hcard#Implied_.22n.22_Optimization */
        virtualGetter: function (mfnode) {
            var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
            var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
            var given_name = [];
            var family_name = [];
            if (fn && (!orgs || (orgs.length > 1) || (fn != orgs[0]["organization-name"]))) {
                var fns = fn.split(" ");
                if (fns.length === 2) {
                    if (fns[0].charAt(fns[0].length - 1) == ',') {
                        given_name[0] = fns[1];
                        family_name[0] = fns[0].substr(0, fns[0].length - 1);
                    } else if (fns[1].length == 1) {
                        given_name[0] = fns[1];
                        family_name[0] = fns[0];
                    } else if ((fns[1].length == 2) && (fns[1].charAt(fns[1].length - 1) == '.')) {
                        given_name[0] = fns[1];
                        family_name[0] = fns[0];
                    } else {
                        given_name[0] = fns[0];
                        family_name[0] = fns[1];
                    }
                    return { "given-name": given_name, "family-name": family_name };
                }
            }
            return undefined;
        }
    },
    "nickname": {
        plural: true,
        virtual: true,
        /* Implied "nickname" Optimization */
        /* http://microformats.org/wiki/hcard#Implied_.22nickname.22_Optimization */
        virtualGetter: function (mfnode) {
            var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
            var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
            var given_name;
            var family_name;
            if (fn && (!orgs || (orgs.length) > 1 || (fn != orgs[0]["organization-name"]))) {
                var fns = fn.split(" ");
                if (fns.length === 1) {
                    return [fns[0]];
                }
            }
            return undefined;
        }
    },
    "note": {
        plural: true,
        datatype: "HTML"
    },
    "org": {
        subproperties: {
            "organization-name": {
                virtual: true
            },
            "organization-unit": {
                plural: true
            }
        },
        plural: true
    },
    "photo": {
        plural: true,
        datatype: "anyURI"
    },
    "rev": {
        datatype: "dateTime"
    },
    "role": {
        plural: true
    },
    "sequence": {
},
"sort-string": {
},
"sound": {
    plural: true
},
"title": {
    plural: true
},
"tel": {
    subproperties: {
        "type": {
            plural: true,
            values: ["msg", "home", "work", "pref", "voice", "fax", "cell", "video", "pager", "bbs", "car", "isdn", "pcs"]
        },
        "value": {
            datatype: "tel",
            virtual: true
        }
    },
    plural: true
},
"tz": {
},
"uid": {
    datatype: "anyURI"
},
"url": {
    plural: true,
    datatype: "anyURI"
}
}
};

Microformats.add("hCard", hCard_definition);

function hCalendar(node, validate) {
    if (node) {
        Microformats.parser.newMicroformat(this, node, "hCalendar", validate);
    }
}
hCalendar.prototype.toString = function () {
    if (this.resolvedNode) {
        /* If this microformat has an include pattern, put the */
        /* dtstart in parenthesis after the summary to differentiate */
        /* them. */
        var summaries = Microformats.getElementsByClassName(this.node, "summary");
        if (summaries.length === 0) {
            if (this.summary) {
                if (this.dtstart) {
                    return this.summary + " (" + Microformats.dateFromISO8601(this.dtstart).toLocaleString() + ")";
                }
            }
        }
    }
    if (this.dtstart) {
        return this.summary;
    }
    return undefined;
}

var hCalendar_definition = {
    mfObject: hCalendar,
    className: "vevent",
    required: ["summary", "dtstart"],
    properties: {
        "category": {
            plural: true,
            datatype: "microformat",
            microformat: "tag",
            microformat_property: "tag"
        },
        "class": {
            values: ["public", "private", "confidential"]
        },
        "description": {
            datatype: "HTML"
        },
        "dtstart": {
            datatype: "dateTime"
        },
        "dtend": {
            datatype: "dateTime",
            virtual: true,
            /* This will only be called in the virtual case */
            /* If we got here, we have a dtend time without date */
            virtualGetter: function (mfnode) {
                var dtends = Microformats.getElementsByClassName(mfnode, "dtend");
                if (dtends.length == 0) {
                    return undefined;
                }
                var dtend = Microformats.parser.dateTimeGetter(dtends[0], mfnode, true);
                var dtstarts = Microformats.getElementsByClassName(mfnode, "dtstart");
                if (dtstarts.length > 0) {
                    var dtstart = Microformats.parser.dateTimeGetter(dtstarts[0], mfnode);
                    if (dtstart.match("T")) {
                        return Microformats.parser.normalizeISO8601(dtstart.split("T")[0] + "T" + dtend);
                    }
                }
                return undefined;
            }
        },
        "dtstamp": {
            datatype: "dateTime"
        },
        "duration": {
    },
    "geo": {
        datatype: "microformat",
        microformat: "geo"
    },
    "location": {
        datatype: "microformat",
        microformat: "hCard"
    },
    "status": {
        values: ["tentative", "confirmed", "cancelled"]
    },
    "summary": {},
    "transp": {
        values: ["opaque", "transparent"]
    },
    "uid": {
        datatype: "anyURI"
    },
    "url": {
        datatype: "anyURI"
    },
    "last-modified": {
        datatype: "dateTime"
    },
    "rrule": {
        subproperties: {
            "interval": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "interval");
                }
            },
            "freq": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "freq");
                }
            },
            "bysecond": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bysecond");
                }
            },
            "byminute": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byminute");
                }
            },
            "byhour": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byhour");
                }
            },
            "bymonthday": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonthday");
                }
            },
            "byyearday": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byyearday");
                }
            },
            "byweekno": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byweekno");
                }
            },
            "bymonth": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonth");
                }
            },
            "byday": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byday");
                }
            },
            "until": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "until");
                }
            },
            "count": {
                virtual: true,
                /* This will only be called in the virtual case */
                virtualGetter: function (mfnode) {
                    return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "count");
                }
            }
        },
        retrieve: function (mfnode, property) {
            var value = Microformats.parser.textGetter(mfnode);
            var rrule;
            rrule = value.split(';');
            for (var i = 0; i < rrule.length; i++) {
                if (rrule[i].match(property)) {
                    return rrule[i].split('=')[1];
                }
            }
            return undefined;
        }
    }
}
};

Microformats.add("hCalendar", hCalendar_definition);





function geo(node, validate) {
    if (node) {
        Microformats.parser.newMicroformat(this, node, "geo", validate);
    }
}
geo.prototype.toString = function () {
    function collapseWhitespace(instring) {
        instring = instring.replace(/[\t\n\r ]+/g, " ");
        if (instring.charAt(0) == " ")
            instring = instring.substring(1, instring.length);
        if (instring.charAt(instring.length - 1) == " ")
            instring = instring.substring(0, instring.length - 1);
        return instring;
    }

    if (this.latitude != undefined) {
        if (!isFinite(this.latitude) || (this.latitude > 360) || (this.latitude < -360)) {
            return undefined;
        }
    }
    if (this.longitude != undefined) {
        if (!isFinite(this.longitude) || (this.longitude > 360) || (this.longitude < -360)) {
            return undefined;
        }
    }

    if ((this.latitude != undefined) && (this.longitude != undefined)) {
        var s;
        var valueTitles = Microformats.getElementsByClassName(this.node, "value-title");
        if ((this.node.localName.toLowerCase() == "abbr") ||
		(this.node.localName.toLowerCase() == "html:abbr") ||
		(valueTitles.length > 0)) {
            s = collapseWhitespace(Microformats.getTextContent(this.node));
        }

        if (s) {
            return s;
        }

        /* check if geo is contained in a vcard */
        var xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vcard ')][1]";
        document.write('toString: ' + xpathExpression + '<br />');
        var xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        if (xpathResult.singleNodeValue) {
            var hcard = new hCard(xpathResult.singleNodeValue);
            if (hcard.fn) {
                return hcard.fn;
            }
        }
        /* check if geo is contained in a vevent */
        xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vevent ')][1]";
        document.write('toString: ' + xpathExpression + '<br />');
        xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, xpathResult);
        if (xpathResult.singleNodeValue) {
            var hcal = new hCalendar(xpathResult.singleNodeValue);
            if (hcal.summary) {
                return hcal.summary;
            }
        }
        return this.latitude + ", " + this.longitude;
    }
    return undefined;
}

var geo_definition = {
    mfObject: geo,
    className: "geo",
    required: ["latitude", "longitude"],
    properties: {
        "latitude": {
            datatype: "float",
            virtual: true,
            /* This will only be called in the virtual case */
            virtualGetter: function (mfnode) {
                var value = Microformats.parser.textGetter(mfnode);
                var latlong;
                if (value && value.match(';')) {
                    latlong = value.split(';');
                    if (latlong[0]) {
                        if (!isNaN(latlong[0])) {
                            return parseFloat(latlong[0]);
                        }
                    }
                }
                return undefined;
            }
        },
        "longitude": {
            datatype: "float",
            virtual: true,
            /* This will only be called in the virtual case */
            virtualGetter: function (mfnode) {
                var value = Microformats.parser.textGetter(mfnode);
                var latlong;
                if (value && value.match(';')) {
                    latlong = value.split(';');
                    if (latlong[1]) {
                        if (!isNaN(latlong[1])) {
                            return parseFloat(latlong[1]);
                        }
                    }
                }
                return undefined;
            }
        }
    },
    validate: function (node) {
        var latitude = Microformats.parser.getMicroformatProperty(node, "geo", "latitude");
        var longitude = Microformats.parser.getMicroformatProperty(node, "geo", "longitude");
        if (latitude != undefined) {
            if (!isFinite(latitude) || (latitude > 360) || (latitude < -360)) {
                throw ("Invalid latitude");
            }
        } else {
            throw ("No latitude specified");
        }
        if (longitude != undefined) {
            if (!isFinite(longitude) || (longitude > 360) || (longitude < -360)) {
                throw ("Invalid longitude");
            }
        } else {
            throw ("No longitude specified");
        }
        return true;
    }
};

Microformats.add("geo", geo_definition);

function tag(node, validate) {
    if (node) {
        Microformats.parser.newMicroformat(this, node, "tag", validate);
    }
}
tag.prototype.toString = function () {
    return this.tag;
}

var tag_definition = {
    mfObject: tag,
    attributeName: "rel",
    attributeValues: "tag",
    properties: {
        "tag": {
            virtual: true,
            virtualGetter: function (mfnode) {
                if (mfnode.href) {
                    var href = mfnode.href.split("?")[0].split("#")[0];
                    var url_array = href.split("/");
                    for (var i = url_array.length - 1; i > 0; i--) {
                        if (url_array[i] !== "") {
                            var tag = Microformats.tag.validTagName(url_array[i].replace(/\+/g, ' '));
                            if (tag) {
                                try {
                                    return decodeURIComponent(tag);
                                } catch (ex) {
                                    return unescape(tag);
                                }
                            }
                        }
                    }
                }
                return null;
            }
        },
        "link": {
            virtual: true,
            datatype: "anyURI"
        },
        "text": {
            virtual: true
        }
    },
    validTagName: function (tag) {
        var returnTag = tag;
        if (tag.indexOf('?') != -1) {
            if (tag.indexOf('?') === 0) {
                return false;
            } else {
                returnTag = tag.substr(0, tag.indexOf('?'));
            }
        }
        if (tag.indexOf('#') != -1) {
            if (tag.indexOf('#') === 0) {
                return false;
            } else {
                returnTag = tag.substr(0, tag.indexOf('#'));
            }
        }
        if (tag.indexOf('.html') != -1) {
            if (tag.indexOf('.html') == tag.length - 5) {
                return false;
            }
        }
        return returnTag;
    },
    validate: function (node) {
        var tag = Microformats.parser.getMicroformatProperty(node, "tag", "tag");
        if (!tag) {
            if (node.href) {
                var url_array = node.getAttribute("href").split("/");
                for (var i = url_array.length - 1; i > 0; i--) {
                    if (url_array[i] !== "") {
                        throw ("Invalid tag name (" + url_array[i] + ")"); ;
                    }
                }
            } else {
                throw ("No href specified on tag");
            }
        }
        return true;
    }
};

Microformats.add("tag", tag_definition);



// ------------------------------------------------------



function vCard(hcard, lineending) {
    var crlf = "\r\n";
    if (lineending) {
        crlf = lineending;
    }
    var vcf;
    var i;
    var j;
    vcf = "BEGIN:VCARD" + crlf;
    vcf += "PRODID:-//kaply.com//Operator 0.8//EN" + crlf;
    vcf += "SOURCE:" + document.location.href + crlf;
    vcf += "NAME:" + document.title + crlf;
    vcf += "VERSION:3.0" + crlf;
    if (hcard.n && (hcard.n["family-name"] || hcard.n["given-name"] ||
        hcard.n["additional-name"] || hcard.n["honorific-prefix"] ||
        hcard.n["honorific-suffix"])) {
        vcf += "N;CHARSET=UTF-8:";
        if (hcard.n["family-name"]) {
            vcf += hcard.n["family-name"].join(",");
        }
        vcf += ";";
        if (hcard.n["given-name"]) {
            vcf += hcard.n["given-name"].join(",");
        }
        vcf += ";";
        if (hcard.n["additional-name"]) {
            vcf += hcard.n["additional-name"].join(",");
        }
        vcf += ";";
        if (hcard.n["honorific-prefix"]) {
            vcf += hcard.n["honorific-prefix"].join(",");
        }
        vcf += ";";
        if (hcard.n["honorific-suffix"]) {
            vcf += hcard.n["honorific-suffix"].join(",");
        }
        vcf += crlf;
    } else {
        vcf += "N:;;;;" + crlf;
    }
    if (hcard.org) {
        vcf += "ORG;CHARSET=UTF-8:";
        if (hcard.org[0]["organization-name"]) {
            vcf += hcard.org[0]["organization-name"];
        }
        if (hcard.org[0]["organization-unit"]) {
            vcf += ";";
            vcf += hcard.org[0]["organization-unit"].join(";");
        }
        vcf += crlf;
    }
    if (hcard.fn) {
        vcf += "FN;CHARSET=UTF-8:" + hcard.fn + crlf;
    }
    if (hcard.title) {
        vcf += "TITLE;CHARSET=UTF-8:" + hcard.title[0] + crlf;
    }
    if (hcard.role) {
        vcf += "ROLE;CHARSET=UTF-8:" + hcard.role[0] + crlf;
    }
    if (hcard["sort-string"]) {
        vcf += "SORT-STRING;CHARSET=UTF-8:" + hcard["sort-string"] + crlf;
    }
    if (hcard["class"]) {
        vcf += "CLASS;CHARSET=UTF-8:" + hcard["class"] + crlf;
    }
    if (hcard.tz) {
        vcf += "TZ;CHARSET=UTF-8:" + hcard.tz + crlf;
    }
    if (hcard.category) {
        vcf += "CATEGORIES;CHARSET=UTF-8:" + hcard.category.join(",") + crlf;
    }
    if (hcard.rev) {
        vcf += "REV:" + hcard.rev + crlf;
    }
    if (hcard.bday) {
        vcf += "BDAY:" + hcard.bday + crlf;
    }
    if (hcard.uid) {
        vcf += "UID:" + hcard.uid + crlf;
    } else {
        vcf += "UID:" + crlf;
    }
    if (hcard.url) {
        for (i = 0; i < hcard.url.length; i++) {
            if (/^aim:/.test(hcard.url[i])) {
                vcf += "X-AIM:" + hcard.url[i].split('=')[1] + crlf;
            } else if (/^ymsgr:/.test(hcard.url[i])) {
                vcf += "X-YAHOO:" + hcard.url[i].split('?')[1] + crlf;
            } else if (/^msnim:/.test(hcard.url[i])) {
                vcf += "X-MSN:" + hcard.url[i].split('=')[1] + crlf;
            } else if (/^skype/.test(hcard.url[i])) {
                vcf += "X-SKYPE:" + hcard.url[i].split(':')[1].split('?')[0] + crlf;
            }
            vcf += "URL:" + hcard.url[i] + crlf;
        }
    }
    if (hcard.email) {
        for (i = 0; i < hcard.email.length; i++) {
            vcf += "EMAIL";
            if (hcard.email[i].type) {
                vcf += ";TYPE=";
                vcf += hcard.email[i].type.join(",");
            }
            vcf += ":";
            vcf += hcard.email[i].value;
            vcf += crlf;
        }
    }
    if (hcard.adr) {
        for (i = 0; i < hcard.adr.length; i++) {
            vcf += "ADR;CHARSET=UTF-8";
            if (hcard.adr[i].type) {
                vcf += ";TYPE=";
                vcf += hcard.adr[i].type.join(",");
            }
            vcf += ":";
            if (hcard.adr[i]["post-office-box"]) {
                vcf += hcard.adr[i]["post-office-box"];
            }
            vcf += ";";
            if (hcard.adr[i]["extended-address"]) {
                vcf += hcard.adr[i]["extended-address"];
            }
            vcf += ";";
            if (hcard.adr[i]["street-address"]) {
                vcf += hcard.adr[i]["street-address"].join(",");
            }
            vcf += ";";
            if (hcard.adr[i].locality) {
                vcf += hcard.adr[i].locality;
            }
            vcf += ";";
            if (hcard.adr[i].region) {
                vcf += hcard.adr[i].region;
            }
            vcf += ";";
            if (hcard.adr[i]["postal-code"]) {
                vcf += hcard.adr[i]["postal-code"];
            }
            vcf += ";";
            if (hcard.adr[i]["country-name"]) {
                vcf += hcard.adr[i]["country-name"];
            }
            vcf += crlf;
        }
    }
    if (hcard.tel) {
        for (i = 0; i < hcard.tel.length; i++) {
            vcf += "TEL;TYPE=";
            if (hcard.tel[i].type) {
                vcf += hcard.tel[i].type.join(",");
            } else {
                /* Default to voice if there is no type */
                vcf += "VOICE";
            }
            vcf += ":";
            vcf += hcard.tel[i].value;
            vcf += crlf;
        }
    }
    if (hcard.geo) {
        vcf += "GEO:" + hcard.geo.latitude + ";" + hcard.geo.longitude + crlf;
    }
    if (hcard.note) {
        vcf += "NOTE;CHARSET=UTF-8:";
        for (i = 0; i < hcard.note.length; i++) {
            var s = hcard.note[i].toString();
            if (!s) {
                continue;
            }
            s = s.replace(/\<.*?\>/gi, ' ');
            s = s.replace(/[\n\r\t]/gi, ' ');
            s = s.replace(/\s{2,}/gi, ' ');
            s = s.replace(/\s{2,}/gi, '');
            s = s.replace(/^\s+/, '');
            if (i != 0) {
                vcf += " ";
            }
            vcf += s;
        }
        vcf += crlf;
    }
    if (hcard.nickname) {
        vcf += "NICKNAME;CHARSET=UTF-8:" + hcard.nickname + crlf;
    }
    /* Add code to handle data URLs */
    if (hcard.photo) {
        vcf += "PHOTO;VALUE=uri:" + hcard.photo + crlf;
    }
    if (hcard.logo) {
        vcf += "LOGO;VALUE=uri:" + hcard.logo + crlf;
    }
    if (hcard.label) {
        vcf += "LABEL:" + hcard.label + crlf;
    }
    vcf += "END:VCARD" + crlf;
    return vcf;
}